//
// Created by admin on 2022/9/8.
//

#ifndef LEARN_OPENCV_TRACKVIDEO_HPP
#define LEARN_OPENCV_TRACKVIDEO_HPP

#include "opencv2/opencv.hpp"
#include<iostream>

namespace TrackVideo {


    bool selectObject = false; //用于是否有选取目标
    int trackObject = 0; // 1表示有追踪对象 0 表示无追踪对象
    volatile int pause = 0; // 0标识未暂停，1暂停
    int speed = 1; // 读帧数频率
    int frameRate = 1000; // 读帧数频率
    //-1表示追踪对象尚未计算camshift所需的属性
    cv::Rect selection;//保存鼠标选择的区域
    cv::Mat image;//用于缓存读取到的视频帧
/*
opencv 对所注册的鼠标回调函数定义为：
void onMouse(int event,int x,int y,int flag,void * param)
其中第四个flag为event下的附加状态，param是用户传入的参数
*/
    void onMouse(int event, int x, int y, int, void *) {
        using namespace cv;
        static cv::Point origin;
        /*static关键字的作用：
          1、隐藏功能 利用这一特性可以在不同的文件中定义同名函数和同名变量
          2、保持变量内容的持久
        */
        if (selectObject) {//如果检测到选取目标进行下面的操作
            //确定鼠标选定区域的左上角坐标以及区域的长和宽
            selection.x = MIN(x, origin.x);
            selection.y = MIN(y, origin.y);
            selection.width = std::abs(x - origin.x);
            selection.height = std::abs(y - origin.y);
            //&运算符被cv::Rect进行重载
            //表示两个区域的交集，主要目的是为了处理当鼠标在选择区域时移除画面外
            selection &= cv::Rect(0, 0, image.cols, image.rows);
        }
        switch (event) {
            //处理鼠标左键被按下
            case EVENT_LBUTTONDOWN:
                origin = cv::Point(x, y);
                selection = cv::Rect(x, y, 0, 0);
                selectObject = true;
                break;
                //处理鼠标左键被抬起
            case EVENT_LBUTTONUP:
                selectObject = false;
                if (selection.width > 0 && selection.height > 0)
                    trackObject = -1;//追踪的目标还未计算camshift所需要的属性
                break;

        }
    }

    bool readNFrame(cv::VideoCapture &video, cv::Mat &mat, int n) {
        bool isOk = true;
        for (int i = 0; i < n; ++i) {
            isOk = video.read(mat);
        }
        return isOk;
    }

    bool keyControl() {
        char code = (char) cv::waitKey(1000 / frameRate);
        if (code > 1)std::cout << code << (int) code << std::endl;
        switch (code) {
            case 27:
                return true;
            case 100:
                if (frameRate + 100 > 1000) {
                    frameRate = 1000;
                    speed *= 2;

                } else frameRate += 100;
                break;
            case 97:
                if (speed / 2) {
                    speed /= 2;
                } else if (frameRate - 100) {
                    frameRate -= 100;
                } else {
                    frameRate = 1;
                }
                break;
        }
        std::cout << speed << std::endl;
        return false;
    }

    int testTrack1(const char *fileName) {
        cv::VideoCapture video;//读取文件
        //如果需要使用摄像头则代码为
        //cv::VideoCapture video(0);
        if (fileName == nullptr) {
            video.open(0);
        } else {
            video.open(fileName);
        }

        cv::namedWindow("test");
        //注册鼠标事件的回调函数，第三个参数是用户提供给回掉函数的
        //也就是回调函数中最后的param参数
        cv::setMouseCallback("test", onMouse, 0);

/*
捕获画面的容器，opencv中的mat对象
opencv中最关键的mat类，mat是matrix的缩写
opencv中延续了像素图的概念，用矩阵来描述由像素构成的图像
*/
        cv::Mat frame, hsv, hue, mask, hist, backproj;
        cv::Rect trackWindow; //追踪到的窗口
        int hsize = 16;//计算直方图所必备的内容
        float hranges[] = {0, 180};//计算直方图所必备的内容
        const float *phranges = hranges;//计算直方图所必备的内容

        while (readNFrame(video, image, speed)) {
            //            while (pause) {
//                Sleep(1000);
//            }
            //将frame中的图像写入全局变量image作为进行camshift的缓存
            cv::resize(image, image, cv::Size(1280, 960));
//            std::cout << image.cols << "," << image.rows << std::endl;
            //转换到的HSV空间
            cv::cvtColor(image, hsv, cv::COLOR_BGR2HSV);
            //当有目标时候进行处理
            if (trackObject) {
                // 只处理像素值为H：0~180，S：30~256，V：10~256之间的部分，过滤掉其他的部分并复制给 mask
                cv::inRange(hsv, cv::Scalar(0, 30, 10), cv::Scalar(180, 256, 256), mask);
                //下面三句将hsv图像中的H通道分离出来
                int ch[] = {0, 0};
                hue.create(hsv.size(), hsv.depth());
                cv::mixChannels(&hsv, 1, &hue, 1, ch, 1);
                //如果需要追踪的物体还没有进行属性提取，则对选择的目标中的图像属性提取
                if (trackObject < 0) {
                    //设置H通道和mask图像的ROI
                    cv::Mat roi(hue, selection), maskroi(mask, selection);
                    //计算ROI所在区域的直方图
                    calcHist(&roi, 1, 0, maskroi, hist, 1, &hsize, &phranges);
                    //将直方图归一
                    normalize(hist, hist, 0, 255, cv::NormTypes::NORM_MINMAX);
                    //设置追踪的窗口
                    trackWindow = selection;
                    //标记追踪的目标已经计算过直方图属性
                    trackObject = 1;
                }
                //将直方图进行反向投影
                calcBackProject(&hue, 1, 0, hist, backproj, &phranges);
                //取公共部分
                backproj &= mask;
                //调用camshift算法的接口
                cv::RotatedRect trackBox = CamShift(backproj, trackWindow, cv::TermCriteria(1 | 2, 10, 1));
                //处理追踪面积过小的情况
                if (trackWindow.area() <= 1) {
                    int cols = backproj.cols, rows = backproj.rows, r = (MIN(cols, rows) + 5) / 6;
                    trackWindow = cv::Rect(trackWindow.x - r, trackWindow.y - r,
                                           trackWindow.x + r, trackWindow.y + r) &
                                  cv::Rect(0, 0, cols, rows);
                }
                //绘制追踪区域
                ellipse(image, trackBox, cv::Scalar(0, 0, 255), 2, cv::LineTypes::LINE_AA);

            }
            //如果正在选择追踪目标，则画出选择框
            if (selectObject && selection.width > 0 && selection.height > 0) {
                cv::Mat roi(image, selection);
                bitwise_not(roi, roi);//对选择的区域图像反色
            }

            imshow("test", image);//显示当前帧
            //            cv::resizeWindow("test",500,600);

            // 录制视频帧率为 15, 等待 1000/15 保证视频播放流畅。
            // waitKey(int delay) 是 OpenCV 提供的一个等待函数，
            // 当运行到这个函数时会阻塞 delay 毫秒的时间来等待键盘输入
            if (keyControl())break;
        }
        //释放申请的相关内存
        cv::destroyAllWindows();
        video.release();
        return 0;
    }


    int testTrack() {
        using namespace cv;
        VideoCapture capture(0);
        Mat frame;
        //保存目标轨迹
        std::vector<Point> pt;
        //capture.open(0);
//        frame = capture.open("D:\\BaiduNetdiskDownload\\cxk.avi");
//        for (int i = 0; i < 100; ++i) {
//            capture >> frame;
//        }
        if (!capture.isOpened()) {
            printf("can not open camera \n");
            return -1;
        }
        namedWindow("input", WINDOW_AUTOSIZE);
        namedWindow("output", WINDOW_AUTOSIZE);
        capture.read(frame);
        if (frame.empty())
            return -1;
        Rect2d first = selectROI("output", frame);
        Rect selectionROI;
        selectionROI.width = first.width;
        selectionROI.height = first.height;
        selectionROI.x = first.x;
        selectionROI.y = first.y;
        printf("x= %d, y=%d, width=%d, height=%d", selectionROI.x, selectionROI.y, selectionROI.width,
               selectionROI.height);

        Mat mask, hist, backproject;
        int bins = 120;
        Mat drawImg = Mat::zeros(300, 300, CV_8UC3);

        while (capture.read(frame)) {
            Mat hsvimage;
            cvtColor(frame, hsvimage, COLOR_BGR2HSV);
            inRange(hsvimage, Scalar(25, 43, 46), Scalar(35, 256, 256), mask);
            Mat hue = Mat(hsvimage.size(), hsvimage.depth());
            int channels[] = {0, 0};
            mixChannels(&hsvimage, 1, &hue, 1, channels, 1);

            //ROI直方图计算
            Mat roi(hue, first);
            Mat maskroi(mask, first);
            float hrange[] = {0, 180};
            const float *hranges = hrange;
            //直方图
            calcHist(&roi, 1, 0, maskroi, hist, 1, &bins, &hranges);
            normalize(hist, hist, 0, 255, NORM_MINMAX);


            int binw = drawImg.cols / bins;
            Mat colorIndex = Mat(1, bins, CV_8UC3);
            for (int i = 0; i < bins; i++) {
                colorIndex.at<Vec3b>(0, i) = Vec3b(saturate_cast<uchar>(i * 180 / bins), 255, 255);
            }
            cvtColor(colorIndex, colorIndex, COLOR_HSV2BGR);
            for (int i = 0; i < bins; i++) {
                int val = saturate_cast<int>(hist.at<float>(i) * drawImg.rows / 255);
                rectangle(drawImg, Point(i * binw, drawImg.rows), Point((i + 1) * binw, drawImg.rows * val),
                          Scalar(colorIndex.at<Vec3b>(0, i)), -1, 8, 0);
            }

            //计算直方图的反投影
            calcBackProject(&hue, 1, 0, hist, backproject, &hranges);
            backproject &= mask;
            RotatedRect trackBox = CamShift(backproject, selectionROI,
                                            TermCriteria((TermCriteria::COUNT | TermCriteria::EPS), 10, 1));
            Rect rect;
            rect.x = trackBox.center.x - trackBox.size.width / 2.0;
            rect.y = trackBox.center.y - trackBox.size.height / 2.0;
            rect.width = trackBox.size.width;
            rect.height = trackBox.size.height;

            rectangle(frame, rect, Scalar(255, 255, 0), 3);

            pt.push_back(Point(rect.x + rect.width / 2, rect.y + rect.height / 2));
            for (int i = 0; i < pt.size() - 1; i++) {
                line(frame, pt[i], pt[i + 1], Scalar(0, 255, 0), 2.5);
            }
            imshow("input", frame);
            imshow("output", drawImg);
            waitKey(10);
        }

        capture.release();
        return 0;
    }
}
#endif //LEARN_OPENCV_TRACKVIDEO_HPP
